import {
    addIcon, App, HeadingCache, Modal, Notice,
    Plugin, PluginSettingTab, Setting, TFile, TFolder, Vault
} from "obsidian";
import * as Path from "path";
import {ExecuteUI} from "./ui";
import {JobDef} from "./common";
import {SettingTab} from "./settings";

interface PluginSettings {
    jobs: Array<JobDef>,
    recursivelyJobs: Array<JobDef>,
    // headers: Array<[string, string, string, string, boolean]>,
    // locations: Array<string>,
    // outputs: Array<string>,
    // enables: Array<string>,
    populateEmptyTitle: boolean,
    titleLinks: boolean,
    includeSubHeadings: boolean,
}

const DEFAULT_SETTINGS: PluginSettings = {
    jobs: [new JobDef()],
    recursivelyJobs: [new JobDef()],
    // headers: [""],
    // locations: [""],
    // outputs: [""],
    // enables: [""],
    populateEmptyTitle: false,
    titleLinks: true,
    includeSubHeadings: true,
}

//Add chevron-up-square icon from lucide for mobile toolbar (temporary until Obsidian updates to Lucide v0.130.0)
addIcon("chevron-up-square", `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-up-square"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"></rect><polyline points="8,14 12,10 16,14"></polyline></svg>`);

export default class DailyNotesMergePlugin extends Plugin {
    settings: PluginSettings

    async onload() {
        await this.loadSettings();
        const executeUI: Modal = new ExecuteUI(this.app, this);
        addIcon("combine", `<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-combine-icon lucide-combine"><path d="M10 18H5a3 3 0 0 1-3-3v-1"/><path d="M14 2a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2"/><path d="M20 2a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2"/><path d="m7 21 3-3-3-3"/><rect x="14" y="14" width="8" height="8" rx="2"/><rect x="2" y="2" width="8" height="8" rx="2"/></svg>`);
        const ribbonIconEl = this.addRibbonIcon('combine', 'Daily Notes Merge', (evt: MouseEvent) => {
            executeUI.open();
        });
        this.addCommand({
            id: "merge-daily-notes",
            name: "Start to merge daily notes",
            icon: "plus-square",
            callback: () => {
                this.executeAllSingleJobs(this.settings.jobs);
            },
        });

        // this.addCommand({
        //     id: "merge-daily-notes2",
        //     name: "generate jobs",
        //     icon: "plus-square",
        //     callback: () => {
        //         this.generateSubHeadingJobs();
        //     },
        // });

        // add settings tab
        this.addSettingTab(new SettingTab(this.app, this));
    }

    async loadSettings() {
        this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
    }

    async saveSettings() {
        await this.saveData(this.settings);
    }

    headingToString(heading: HeadingCache): string {
        return "#".repeat(heading.level) + ` ${heading.heading}`
    }

    getMarkdownFiles(startLocation: string) : TFile[] {
        const files: TFile[] = [];
        const folderOrFile = this.app.vault.getAbstractFileByPath(startLocation);
        // const folderOrFile = this.app.vault.getAbstractFileByPath("1_Inbox/Diary/2023-07-28.md");
        if (folderOrFile instanceof TFile) {
            files.push(folderOrFile);
        } else if (folderOrFile instanceof TFolder) {
            Vault.recurseChildren(folderOrFile, (file => {
                if (file instanceof TFile && file.extension === "md") {
                    files.push(file);
                }
            }));
        }
        files.sort((a, b)=>{
            if (a.basename.toLowerCase() > b.basename.toLowerCase()) return -1;
            if (a.basename.toLowerCase() < b.basename.toLowerCase()) return 1;
            return 0;
        });
        return files;
    }

    buildHeadingTextTree(scanStartPath: string): Map<string, string[]> {
        const files: TFile[] = this.getMarkdownFiles(scanStartPath);
        const subHeadingTextMap: Map<string, string[]> = new Map();
        for (const file of files) {
            const cacheItem = this.app.metadataCache.getCache(file.path);
            if (!cacheItem.headings) continue;
            for (let i = 0; i <= cacheItem.headings.length; i++) {
                const heading1 = cacheItem.headings[i];
                // const subHeadings: HeadingCache[] = [];
                for (let j = i + 1; j <= cacheItem.headings.length; j++) {
                    const heading2 = cacheItem.headings[j];
                    if (heading2) {
                        if (heading2.level == heading1.level + 1){
                            if (!subHeadingTextMap.get(heading1.heading)) subHeadingTextMap.set(heading1.heading, []);
                            // subHeadings.push(heading2);
                            // subHeadingsMap.set(this.headingToString(heading1), subHeadings) ;
                            const subHeadingTextArray = subHeadingTextMap.get(heading1.heading);
                            if (!subHeadingTextArray.contains(heading2.heading)) {
                                subHeadingTextArray.push(heading2.heading);
                            }
                            subHeadingTextMap.set(heading1.heading, subHeadingTextArray) ;
                        } else {
                            break;
                        }
                    }
                }
            }
        }
        return subHeadingTextMap;
    }

    async writeToOutputPath(outputPath: string, headingText: string, content: string, openFile: boolean = true) {
        let outputFilePath: string;
        if (outputPath.endsWith(".md")) {
            outputFilePath = outputPath;
        } else {
            outputFilePath = `${outputPath}/${headingText}.md`;
        }
        if (!await this.app.vault.adapter.exists(Path.dirname(outputFilePath))) {
            await this.app.vault.createFolder(Path.dirname(outputFilePath));
        }

        await this.writeAndOpenFile(
            this.app,
            outputFilePath,
            content,
            openFile
        );
    }

    async writeAndOpenFile(app: App, outputFileName: string, text: string, openFile: boolean) {
        await app.vault.adapter.write(outputFileName, text);
        if (!openFile) return;

        let fileIsAlreadyOpened = false;
        app.workspace.iterateAllLeaves((leaf) => {
            if (
                leaf.getDisplayText() != "" &&
                outputFileName.startsWith(leaf.getDisplayText())
            ) {
                fileIsAlreadyOpened = true;
            }
        });
        if (!fileIsAlreadyOpened) {
            const newPane = app.workspace.getLeavesOfType("empty").length == 0;
            if (newPane) {
                app.workspace.openLinkText(outputFileName, "/", true);
            } else {
                const file = app.vault.getAbstractFileByPath(outputFileName);

                if (file instanceof TFile) {
                    await app.workspace
                        .getLeavesOfType("empty")[0]
                        .openFile(file);
                } else {
                    app.workspace.openLinkText(outputFileName, "/", true);
                }
            }
        }
    }

    async mergeDailyNotesHeadingRecursively(headingText: string, parentHeadingText:string, scanRangePath: string, outputPath: string, headingTextTreeMap: Map<string, string[]>) {
        const subHeadingTextArray = headingTextTreeMap.get(headingText);
        // const subHeadingContentArray: string[] = [];
        if (subHeadingTextArray && subHeadingTextArray.length > 0) {
            // 表示有下级heading
            for (const subHeadingText of subHeadingTextArray) {
                await this.mergeDailyNotesHeadingRecursively(subHeadingText, headingText, scanRangePath, outputPath, headingTextTreeMap);
                // subHeadingContentArray.push(subHeadingText);
            }
        }
        // 叶子了
        const job = new JobDef();
        job.headingText = headingText;
        job.scanRangePath = scanRangePath;
        job.outputPath = `${outputPath}/${headingText}.md`;
        job.enabled = true;

        let subHeadingContent = await this.mergeDailyNotesHeading(job.headingText, job.scanRangePath, this.settings.includeSubHeadings, job.enabled);
        subHeadingContent = "===\n\n" + subHeadingContent;

        if (subHeadingTextArray && subHeadingTextArray.length > 0) {
            for (const si of subHeadingTextArray) {
                subHeadingContent = `* >>> [[${si}]]` + "\n" + subHeadingContent;
            }
        }
        if (parentHeadingText){
            subHeadingContent = `* <<< [[${parentHeadingText}]]` + "\n" + subHeadingContent;
        }
        subHeadingContent = "===\n" + subHeadingContent;
        this.writeToOutputPath(
            job.outputPath,
            job.headingText,
            subHeadingContent,
            false
        );
        return subHeadingContent;
    }

    // async executeRecursivelyJob(headingText: string, scanRangePath: string, outputPath: string, enabled: boolean){
    async executeRecursivelyJob(job: JobDef, overriddenEnabled: boolean = false){
        const jobEnabled = overriddenEnabled ? true : job.enabled;
        if (!jobEnabled)
            return;
        const headingTextTreeMap = this.buildHeadingTextTree(job.scanRangePath);
        await this.mergeDailyNotesHeadingRecursively(job.headingText, null, job.scanRangePath, job.outputPath, headingTextTreeMap);
        new Notice(`Done! outputPath: ${job.outputPath}`);
    }

    async executeSingeJob(job: JobDef, overriddenEnabled: boolean = false) {
        const jobEnabled = overriddenEnabled ? true : job.enabled;
        const content = await this.mergeDailyNotesHeading(job.headingText, job.scanRangePath, this.settings.includeSubHeadings, jobEnabled);
        if (content) {
            this.writeToOutputPath(job.outputPath, job.headingText, content);
            new Notice(`Done! outputPath: ${job.outputPath}`);
        }
    }

    async executeAllSingleJobs(jobs: JobDef[]) {
        // const jobs = this.settings.jobs;
        for (const job of jobs) {
            const content = await this.mergeDailyNotesHeading(job.headingText, job.scanRangePath, this.settings.includeSubHeadings, job.enabled);
            if (content) {
                this.writeToOutputPath(job.outputPath, job.headingText, content);
            }
        }
        new Notice(`Done! all ${jobs.length} jobs.`);
    }

    async mergeDailyNotesHeading(headingText: string, scanRangePath: string, includeSubHeading: boolean, enabled: boolean) {
        if (!enabled)
            return;
        let mergedContent = "";
        const files: TFile[] = this.getMarkdownFiles(scanRangePath);

        for (const file of files) {
            let pairs = [];
            const cacheItem = this.app.metadataCache.getCache(file.path);
            if (!cacheItem.headings) continue;
            for (let i = 0; i <= cacheItem.headings.length; i++) {
                const heading1 = cacheItem.headings[i];
                for (let j = i + 1; j <= cacheItem.headings.length; j++) {
                    const heading2 = cacheItem.headings[j];
                    if (heading2) {
                        if (includeSubHeading) {
                            if (heading2.level <= heading1.level) {
                                pairs.push([heading1, heading2])
                                break;
                            }
                        } else {
                            pairs.push([heading1, heading2]);
                            break;
                        }
                    }
                }
            }

            for (const pair of pairs) {
                if (pair[0].heading === headingText) {
                    const positionStartOffset = pair[0].position.end.offset;
                    const positionEndOffset = pair[1].position.start.offset;

                    const fileContent = await this.app.vault.read(file);
                    let text = fileContent.substring(positionStartOffset, positionEndOffset).trim(); // 2024-03-09 修复没有标题时会多一个换行符的问题
                    if (this.settings.populateEmptyTitle || text.trim() !== ""){
                        let textHeader;
                        if (this.settings.titleLinks)
                            textHeader = `[[${file.basename}]]\n`;
                        else
                            textHeader = `**${file.basename}**\n`;
                        text = textHeader + text.trim() + "\n";
                    }
                    mergedContent += text;
                }
            }
        }

        return mergedContent;
    }
}
